<?php
/**
 * Created by PhpStorm.
 * User: mlboy@126.com
 * Date: 16/8/8
 * Time: 上午11:37
 */
namespace Core;
class Db
{
    const MASTER = 'master';
    const SLAVES = 'slaves';
    const READ = 'read';
    const WRITE = 'write';

    static public $db = array();
    static public $dbPool = array();
    static private $logger = null;
    private $config;
    private $pdo;
    private $operate = self::SLAVES;
    private $transaction = 0;
    private $sQuery;
    private $parameters;
    private $lastSqlSegments = null;
    private $lastQuery;
    private $sqlSegments = array(
        'flags' => array(),
        'field' => array(),
        'join' => array(),
        'where' => array(),
        'table' => array(),
        'groupBy' => array(),
        'having' => array(),
        'orderBy' => array(),
        'limit' => array(),
        'lock' => null,
        'params' => array(),
    );
    private $_debugDumpParams = '';
    const LOCK_X = 1;//排他锁
    const LOCK_S = 2;//共享锁

    private $pdoAttributes = array(
        \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
    );

    static public function getInstance($config = null,$key = 'default') {
        if (!isset(self::$db[$key]) || !self::$db[$key]) {
            self::$db[$key] = new self($config);
        }
        return self::$db[$key];
    }
    public function setPdoAttribute($attributes) {
        $this->pdoAttributes = array_merge($this->pdoAttributes,$attributes);
    }
    public function setMaster($config) {
        $this->config[self::MASTER] = $config;
    }
    public function setSlaves($config) {
        $hash = md5(serialize($config));
        $this->config[self::SLAVES][$hash] = $config;
    }
    static public function setLogger($logger) {
        static::$logger = $logger;
    }
    public function __construct($config = null) {
        if (!is_null($config)) {
            $this->config = $config;
        }

        $this->parameters = array();
    }
    private function connect($master = false) {
        if (
            $master == false
            && $this->operate == self::SLAVES
            && isset($this->config[self::SLAVES])
            && $this->transaction == 0
        ) {
            $this->operate = self::SLAVES;
            $slaves = &$this->config[self::SLAVES];
            if (count($slaves) == count($slaves, 1)){
                $config = &$slaves;
            } else {
                $index = array_rand($this->config[self::SLAVES]);
                $config = &$this->config[self::SLAVES][$index];
            }
            if (isset($this->config[self::MASTER]) && is_array($config)) {
                foreach(array_keys($this->config[self::MASTER]) as $k) {
                    if (!isset($config[$k]) && isset($this->config[self::MASTER][$k])) {
                        $config[$k] = $this->config[self::MASTER][$k]; 
                    }
                }
            }
        } else {
            $this->operate = self::MASTER;
            $config = &$this->config[self::MASTER];
        }
        try {
            if (!isset($config['dsn'])) {
                if (!$config
                    || !isset($config['host'])
                    || !isset($config['dbname'])
                    || empty($config['host'])
                    || empty($config['dbname'])
                ) {
                    throw new \PDOException('db config is error');
                }
                !isset($config['port']) && $config['port'] = 3306;
                $dsn = sprintf("mysql:dbname=%s;host=%s;port=%s",$config['dbname'],$config['host'],$config['port']);
            } else {
                $dsn = $config['dsn'];
            }
            !isset($config['username']) && $config['username'] = null;
            !isset($config['password']) && $config['password'] = null;
            $hashkey = md5($dsn);
            if (!isset(static::$dbPool[$hashkey])) {
                if (!isset(static::$dbPool[$hashkey][$this->operate])) {
                    $db = new \PDO($dsn, $config['username'], $config['password'], array(\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'));
                    foreach($this->pdoAttributes as $k => $v) {
                        $db->setAttribute($k, $v);
                    }
                    //$db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
                    //$db->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false);
                    static::$dbPool[$hashkey][$this->operate] = $db;
                }
                $this->pdo = static::$dbPool[$hashkey];
            }
        } catch (\PDOException $e) {
            $this->logException($e);
        }
    }
    public function closeConnection() {
        $this->pdo[$this->operate] = null;
    }
    private function init($query,$parameters = '',$master = false) {
        $this->connect($master);
        try {
            $this->sQuery = $this->pdo[$this->operate]->prepare($query);
            $this->bindMore($parameters);
            if(!empty($this->parameters)) {
                //print_r($this->parameters);
                foreach ($this->parameters as $param) {
                    $parameters = explode("\x7F",$param);
                    $this->sQuery->bindParam($parameters[0],$parameters[1]);
                }
            }
            ob_start();
            $this->sQuery->debugDumpParams();
			$this->_debugDumpParams .= ob_get_clean();
            $this->success  = $this->sQuery->execute();
        }
        catch (\PDOException $e) {
            $this->logException($e, $query);
        }
        $this->parameters = array();
    }
    private function lastSql($query = '',$parameters = null) {
        if (!is_null($parameters)) {
            $indexed = $parameters == array_values($parameters);
            foreach ($parameters as $k=>$v) {
                if(is_string($v)) $v="'$v'";
                if($indexed) $query=preg_replace('/\?/',$v,$query,1);
                else $query = str_replace(":$k",$v,$query);
            }
        }
        $this->lastQuery = $query;
        return $query;
    }
    public function getLastQuery() {
        return $this->lastQuery;
    }
    public function master($operate = true) {
        if ($operate == true) {
            $this->operate = self::MASTER;
        } else {
            $this->operate = self::SLAVES;
        }
        return $this;
    }
    public function bind($para, $value) {
        if(is_string($para)) {
            $this->parameters[sizeof($this->parameters)] = ":" . $para . "\x7F" . $value;
        } else {
            $this->parameters[sizeof($this->parameters)] = $para . "\x7F" . $value;
        }
    }
    public function bindMore($parray) {
        if(empty($this->parameters) && is_array($parray)) {
            $columns = array_keys($parray);
            foreach ($columns as $i => &$column)	{
                $this->bind($column, $parray[$column]);
            }
        }
    }
    public function judge($query) {
        $rawStatement = explode(' ', $query);
        $statement = strtolower($rawStatement[0]);
        if ($statement === 'select' || $statement === 'show') {
            return self::READ;
        } else if ( $statement === 'insert' || $statement === 'replace' ||  $statement === 'update' || $statement === 'delete' ) {
            return self::WRITE;
        } else {
            return NULL;
        }
    }
    public function query($query,$params = null,$fetch = false) {
        $segments = $this->sqlSegments;
        if (!empty($segments['table'])) {
            $query = str_ireplace(array('@table','@tab','@'), $this->buildTable(), $query);
        }
        if (is_null($params)) {
            $params = $this->sqlSegments['params'];
            $this->initSqlSegment();
        }
        $query = trim($query);
        $this->lastSql($query,$params);
        $judge = $this->judge($query);
        $ret = array();
        $this->init($query,$params,($judge === self::READ)?false:true); 
        if ($fetch === false) {
            return $this;
        }
        if ($judge === self::READ) {
            return $this->fetchAll();
        }
        return $this->rowCount();
    }
    public function fetch() {
        if (!$args = func_get_args()) {
            $args = array('fetch_style' => \PDO::FETCH_ASSOC);
        }
        return $this->fetchCall('fetch',$args);
    }
    public function fetchAll() {
        if (!$args = func_get_args()) {
            $args = array('fetch_style' => \PDO::FETCH_ASSOC);
        }
        return $this->fetchCall('fetchAll',$args);
    }
    public function rowCount() {
        return $this->fetchCall('rowCount',$args);
    }
    private function fetchCall($fetchMethod = 'fetchAll',$args = array()) {
        if (!isset($this->sQuery)) {
            return NULL;
        }
        $sets = array();
        do {
            $sets[] = call_user_func_array(array($this->sQuery,$fetchMethod),$args);
        } while ($this->sQuery->nextRowset());
        return (count($sets) == 1) ? $sets[0] : $sets;
    }
    public function lastInsertId() {
        return $this->pdo[$this->operate]->lastInsertId();
    }
    /**
     * get some column fields
     **/
    public function column($query,$params = null,$field = NULL) {
        $rows = $this->query($query,$params,false)->fetchAll(\PDO::FETCH_BOTH);
        $column = NULL;
        foreach ($rows as $cells) {
            if ($field == NULL) {
                $column[] = $cells[0];
            } else if (isset($cells[$field])) {
                $column[] = $cells[$field];
            }
        }
        return $column;
    }
    /**
     * get some rows
     **/
    public function rows($query,$params = null) {
        return $this->query($query,$params,false)->fetchAll();
    }
    /**
     * get a row
     **/
    public function row($query,$params = null) {
        return $this->query($query,$params,false)->fetch();
    }
    /**
     * get a field
     **/
    public function single($query,$params = null,$field = NULL) {
        return $this->query($query,$params,false)->fetchCall('fetchColumn',array($field));
    }
    /**
     * begin a TX
     **/
    public function beginTransaction() {
        $this->operate = 'master';
        if (!isset($this->pdo[$this->operate])) {
            $this->connect($this->operate);
        };
        if ($this->transaction == 0) {
            $this->pdo[$this->operate]->beginTransaction();
        }
        $this->transaction++;
    }
    /**
     * commit a Tx
     **/
    public function commit() {
        if ($this->transaction == 1) {
            $this->pdo[$this->operate]->commit();
        }
        $this->transaction--;
    }
    /**
     * rollback a Tx
     **/
    public function rollBack() {
        if ($this->transaction > 0) {
            $this->pdo[$this->operate]->rollBack();
            $this->transaction = 0;
        }
    }
    private function logException($e, $sql = "") {
        if (null !== static::$logger) {
            if(!empty($sql)) {
                static::$logger->error('\Core:Db sql:{sql}',array('{sql}'=>$sql));
            }
            static::$logger->error('\Core:Db exception:{exception}',array('{exception}'=>$e));
        }
        throw $e;
        //return $exception;
    }
    private function initSqlSegment() {
        $this->lastSqlSegments = $this->sqlSegments;
        $this->sqlSegments = array(
            'flags' => array(),
            'field' => array(),
            'join' => array(),
            'where' => array(),
            'table' => array(),
            'groupBy' => array(),
            'having' => array(),
            'orderBy' => array(),
            'limit' => array(),
            'lock' => null,
            'params' => array(),
        );
    }
    public function table($name = '',$alias = null) {
        $this->sqlSegments['table'][] = array('name'=>$name,'alias'=>$alias);
        return $this;
    }
    public function field($fields = '*') {
        if (is_string($fields)) {
            $fields = explode(',',$fields);
            $this->sqlSegments['field'] = array_merge($this->sqlSegments['field'],$fields);
        } else {
            $this->sqlSegments['field'] = array_merge($this->sqlSegments['field'],$fields);
        }
        return $this;
    }
    public function join($name,$conditionA = null,$logic = null,$conditionB = null) {
        $this->addBuildJoin(null,$name,$conditionA,$logic,$conditionB);
        return $this;
    }
    public function leftJoin($name,$conditionA = null,$logic = null,$conditionB = null) {
        $this->addBuildJoin('LEFT',$name,$conditionA,$logic,$conditionB);
        return $this;
    }
    public function rightJoin($name,$conditionA = null,$logic = null,$conditionB = null) {
        $this->addBuildJoin('RIGHT',$name,$conditionA,$logic,$conditionB);
        return $this;
    }
    public function innerJoin($name,$conditionA = null,$logic = null,$conditionB = null) {
        $this->addBuildJoin('INNER',$name,$conditionA,$logic,$conditionB);
        return $this;
    }
    public function crossJoin($name,$conditionA = null,$logic = null,$conditionB = null) {
        $this->addBuildJoin('CROSS',$name,$conditionA,$logic,$conditionB);
        return $this;
    }
    public function where($key,$val = null) {
        $this->addBuildClause('where',$key,$val,'AND');
        return $this;
    }
    public function orWhere($key,$val = null) {
        $this->addBuildClause('where',$key,$val,'OR');
        return $this;
    }
    public function flag($flag) {
        $this->sqlSegments['flags'][] = $flag;
        return $this;
    }
    public function groupBy($fields = '') {
        if (is_string($fields)) {
            $fields = explode(',',$fields);
            $this->sqlSegments['groupBy'] = array_merge($this->sqlSegments['groupBy'],$fields);
        } else {
            $this->sqlSegments['groupBy'] = array_merge($this->sqlSegments['groupBy'],$fields);
        }
        return $this;
    }
    public function orderBy($fields = '') {
        if (is_string($fields)) {
            $fields = explode(',',$fields);
            foreach ($fields as &$val) {
                $seg = explode(' ',trim($val,' '));
                $sort = (isset($seg[1]))?(strtolower($seg[1]) == 'desc')?' DESC':' ASC':'';
                $val = $this->buildIdent($seg[0]) . $sort;
            }
            $this->sqlSegments['orderBy'] = array_merge($this->sqlSegments['orderBy'],$fields);
        } else {
            $this->sqlSegments['orderBy'] = array_merge($this->sqlSegments['orderBy'],$fields);
        }
        return $this;
    }
    public function having($key,$val = null,$op = 'AND') {
        $this->addBuildClause('having',$key,$val,$op);
        return $this;
    }
    public function limit($a,$b = null) {
        if($b == null) {
            $limit = explode(',',$a);
            if (count($limit) ==2) {
                $this->sqlSegments['limit']['limit'] = $limit[1];
                $this->sqlSegments['limit']['offset'] = $limit[0];
            } else {
                $this->sqlSegments['limit']['limit'] = $limit[0];
            }
        } else {
            $this->sqlSegments['limit']['limit'] = (int) $b;
            $this->sqlSegments['limit']['offset'] = (int) $a;
        }
        return $this;
    }
    public function offset($offset) {
        $this->sqlSegments['limit']['offset'] = (int) $offset;
        return $this;
    }
    public function lock($mode = LOCK_X) {
        $this->sqlSegments['lock'] = $mode;
        return $this;
    }
    public function xlock() {
        $this->lock(self::LOCK_X);
        return $this;
    }
    public function slock() {
        $this->lock(self::LOCK_S);
        return $this;
    }
    public function union($union) {
        $this->sqlSegments['union'][] = $union;
        return $this;
    }
    public function chaining() {
        return clone($this);
    }
    public function count($field = '*') {
        if ($field !== '*') {
            $field = 'COUNT('.$this->buildIdent($field).') _count_';
        } else {
            $field = 'COUNT(*) _count_';
        }
        $this->sqlSegments['field'] = array_merge($this->sqlSegments['field'],array($field));
        $this->sqlSegments = $this->sqlSegments + $this->lastSqlSegments;
        foreach($this->sqlSegments as $k =>$v) {
            if (empty($v) && empty($this->sqlSegments['table'])) {
                $this->sqlSegments[$k] = $this->lastSqlSegments[$k];
            }
        }
        $query = $this->buildCount();
        $resp = $this->query($query,null,true);
        return isset($resp[0]['_count_'])?$resp[0]['_count_']:0;
    }
    public function explain($t = 'select') {
        $query = 'EXPLAIN '.$this->buildSelect(); 
        return $this->query($query,null,false)->fetch();
    }
    public function select() {
        $query = $this->buildSelect();
        return $this->query($query,null,false);
    }
    public function get() {
        $query = $this->buildSelect();
        return $this->query($query,null,true);
    }
    public function first() {
        $query = $this->buildSelect();
        return $this->query($query,null,false)->fetch();
    }
    public function insert($data) {
        $this->sqlSegments['field'] = $data;
        $query = $this->buildInsert();
        return $this->query($query,null,true);
    }
    public function replace($data) {
        $this->sqlSegments['field'] = $data;
        $query = $this->buildReplace();
        return $this->query($query,null,true);
    }
    public function update($data) {
        $this->sqlSegments['field'] = $data;
        $query = $this->buildUpdate();
        return $this->query($query,null,true);
    }
    public function delete() {
        $query = $this->buildDelete();
        return $this->query($query,null,true);
    }
    private function buildIdent($name ='') {
        return '`'. str_replace('.', '`.`', $name) .'`';
    }
    private function buildPlaceholder($data,$placeholder = '?',$separator =',') {
        if (count($data)>0) {
            $query = implode($separator,array_fill(1,count($data),$placeholder));
        } else {
            $query = '';
        }
        return $query;
    }
    private function buildPairKV($data,$placeholder = ' = ',$separator =',',$key = null,$val = null) {
        $content = array();
        foreach ($data as $k => $v) {
            preg_match('/\[(\+|\-|\*|\/|\%|\^|\|#)\]?(\w*)/',$k,$match);
            if (!$match) {
                $part = $this->buildIdent(($key)?$key:$k) . $placeholder .(($val)?$val:$v);
            } else {
                if ($match[1] == '#') {
                    $part = $this->buildIdent(($key) ? $key : $match[2]) . $placeholder . $v;
                } else {
                    $part = $this->buildIdent(($key) ? $key : $match[2]) . $placeholder . $this->buildIdent(($key) ? $key : $match[2]) . ' ' . $match[1] . ' ' . (($val) ? $val : $v);
                }
            }
            array_push($content,$part);
        }
        return implode($separator,$content);
    }
    private function buildTable() {
        if (!is_array($this->sqlSegments['table']) || count($this->sqlSegments['table']) <1) {
            throw new \Exception('please set a table name use table() function!');
        }
        $tables = array();
        foreach ($this->sqlSegments['table'] as $table) {
            if (is_callable($table['name'])) {
                $subQuery = clone($this);
                $subQuery->initSqlSegment();
                call_user_func_array($table['name'],array($subQuery));
                $tableName ='(' . $subQuery->buildSelect() . ')';
                $this->buildBindParams($subQuery->sqlSegments['params']);
            } else {
                $tableName = $this->buildIdent($table['name']);
            }
            if ($table['alias']) {
                $tableName .= ' AS '.$this->buildIdent($table['alias']);
            }
            array_push($tables,$tableName);
        }
        return ' '.implode(',',$tables);
    }
    private function buildFlags() {
        if (! $this->sqlSegments['flags']) {
            return '';
        }
        return ' ' . implode(' ',$this->sqlSegments['flags']);
    }
    private function buildField() {
        if (empty($this->sqlSegments['field'])) {
            return ' *';
        } else {
            return ' '.implode(',',$this->sqlSegments['field']);
        }
    }
    private function addBuildClause($type,$key,$val,$logic) {
        if(is_callable($key)) {
            $this->sqlSegments[$type][] = array('(', null, $logic);
            call_user_func_array($key,array($this));
            $this->sqlSegments[$type][] = array(')', null, $logic);
        } else {
            $this->sqlSegments[$type][] = array($key, $val, $logic);
        }
    }
    private function buildClause($type) {
        $sql = '';
        foreach ($this->sqlSegments[$type] as $k =>$clause) {
            if ($clause[0] == '(' ) {
                $sql .= ' ' . strtolower($clause[2]) . ' ' . $clause[0];
            } else if($clause[0] == ')') {
                $sql .= ' ' . $clause[0];
            } else {
                if($k == 0 || substr($sql,-1) == '(') {
                    $sql .= ' ' .$this->buildClauseAndBind($clause[0],$clause[1]);
                } else {
                    $sql .= ' ' . strtolower($clause[2]) . ' ' .$this->buildClauseAndBind($clause[0],$clause[1]);
                }
            }
        }
        //echo $sql;
        return !empty($sql)?' WHERE'.$sql:'';
    }
    protected function buildClauseAndBind($key,$value) {
        preg_match('/\[(\>\=|\<\=|\>|\<|\<\>|\!\=|\=|\~|\!\~|like|!like|in|!in|is|!is|exists|!exists|#)\]?([a-zA-Z0-9_.\-\=\s\?\(\)\&\|]*)/',$key,$match);
        if (!$match) {
            $context = $this->buildIdent($key) .' = ?';
            $this->buildBindParams($value);
        } else {
            switch ($match[1]) {
                case '~':
                case 'like':
                    $context = $this->buildIdent($match[2]) .' LIKE ?';
                    $this->buildBindParams($value);
                    break;
                case '!~':
                case '!like':
                    $context = $this->buildIdent($match[2]) .' NOT LIKE ?';
                    $this->buildBindParams($value);
                    break;
                case '>':
                    $context = $this->buildIdent($match[2]) .' > ?';
                    $this->buildBindParams($value);
                    break;
                case '>=':
                    $context = $this->buildIdent($match[2]) .' >= ?';
                    $this->buildBindParams($value);
                    break;
                case '<':
                    $context = $this->buildIdent($match[2]) .' < ?';
                    $this->buildBindParams($value);
                    break;
                case '<=':
                    $context = $this->buildIdent($match[2]) .' <= ?';
                    $this->buildBindParams($value);
                    break;
                case '<>':
                case '!=':
                    $context = $this->buildIdent($match[2]) .' != ?';
                    $this->buildBindParams($value);
                    break;
                case '=':
                    $context = $this->buildIdent($match[2]) .' = ?';
                    $this->buildBindParams($value);
                    break;
                case 'in':
                    $context = $this->buildIdent($match[2]) .' IN(' . $this->buildPlaceholder($value) . ')';
                    $this->buildBindParams($value);
                    break;
                case '!in':
                    $context = $this->buildIdent($match[2]) .' NOT IN(' . $this->buildPlaceholder($value) . ')';
                    $this->buildBindParams($value);
                    break;
                case 'exists':
                    if (is_callable($value)) {
                        $subQuery = clone($this);
                        $subQuery->initSqlSegment();
                        call_user_func_array($value,array($subQuery));
                        $context ='EXISTS(' . $subQuery->buildSelect() . ')';
                        $this->buildBindParams($subQuery->sqlSegments['params']);
                    } else {
                        $context ='EXISTS(' . $value . ')';
                    }
                    break;
                case '!exists':
                    if (is_callable($value)) {
                        $subQuery = clone($this);
                        $subQuery->initSqlSegment();
                        call_user_func_array($value,array($subQuery));
                        $context ='NOT EXISTS(' . $subQuery->buildSelect() . ')';
                        $this->buildBindParams($subQuery->sqlSegments['params']);
                    } else {
                        $context ='NOT EXISTS(' . $value . ')';
                    }
                    break;
                case 'is':
                    if (!isset($value) || $value === null) {
                        $context = $this->buildIdent($match[2]) .' IS NULL';
                    } else {
                        $context = $this->buildIdent($match[2]) .' IS ?';
                        $this->buildBindParams($value);
                    }
                    break;
                case '!is':
                    if (!isset($value) || $value === null) {
                        $context = $this->buildIdent($match[2]) .' IS NOT NULL';
                    } else {
                        $context = $this->buildIdent($match[2]) .' IS NOT ?';
                        $this->buildBindParams($value);
                    }
                    break;
                case '#':
                    $context = $match[2].' '.$value;
                    break;
            }
        }

        return $context;
    }
    private function buildBindParams($value) {
        if (is_array($value)) {
            foreach ($value as $val) {
                $this->sqlSegments['params'][sizeof($this->sqlSegments['params'])+1] = $val;
            }
        } else {
            $this->sqlSegments['params'][sizeof($this->sqlSegments['params'])+1] = $value;
        }
    }
    private function buildGroupBy() {
        if (empty($this->sqlSegments['groupBy'])) {
            return '';
        } else {
            return ' GROUP BY '.implode(',',$this->sqlSegments['groupBy']);
        }
    }
    private function buildOrderBy() {
        if (empty($this->sqlSegments['orderBy'])) {
            return '';
        } else {
            $sql = ' ORDER BY '.implode(',',$this->sqlSegments['orderBy']);
            return $sql;
        }
    }
    private function buildLimit() {
        if (empty($this->sqlSegments['limit'])) {
            return '';
        } else {
            $sql = isset($this->sqlSegments['limit']['limit']) ? ' LIMIT ' . (int) $this->sqlSegments['limit']['limit']:'';
            $sql .= isset($this->sqlSegments['limit']['offset']) ? ' OFFSET ' . (int) $this->sqlSegments['limit']['offset']:'';
            return $sql;
        }
    }
    private function buildUnion() {
        if (empty($this->sqlSegments['union'])) {
            return '';
        } else {
            $context = '';
            foreach ($this->sqlSegments['union'] as $union) {
                if (is_callable($union)) {
                    $subQuery = clone($this);
                    $subQuery->initSqlSegment();
                    call_user_func_array($union,array($subQuery));
                    $context .=' UNION(' . $subQuery->buildSelect() . ')';
                    $this->buildBindParams($subQuery->sqlSegments['params']);
                    unset($subQuery);
                } else {
                    $context .=' UNION(' . $union . ')';
                }
            }
            return $context;
        }
    }
    private function buildForLock() {
        if ($this->sqlSegments['lock'] === self::LOCK_X) {
            return ' FOR UPDATE';
        } else if ($this->sqlSegments['lock'] === self::LOCK_S) {
            return ' LOCK IN SHARE MODE';
        } else {
            return '';
        }
    }
    private function buildCount() {
        return 'SELECT'
        . $this->buildFlags()
        . $this->buildField()
        . ' FROM'
        . $this->buildTable()
        . $this->buildJoin()
        . $this->buildClause('where')
        . $this->buildGroupBy()
        . $this->buildClause('having')
        . $this->buildOrderBy()
        //. $this->buildLimit()
        . $this->buildUnion()
        . $this->buildForLock();
    }
    private function buildSelect() {
        return 'SELECT'
        . $this->buildFlags()
        . $this->buildField()
        . ' FROM'
        . $this->buildTable()
        . $this->buildJoin()
        . $this->buildClause('where')
        . $this->buildGroupBy()
        . $this->buildClause('having')
        . $this->buildOrderBy()
        . $this->buildLimit()
        . $this->buildUnion()
        . $this->buildForLock();
    }
    private function buildValuesForInsert() {
        $sql = ' (';
        $data = $this->sqlSegments['field'];
        if (count($data) == count($data, 1)) {
            $sql .= '`' . implode('`,`',array_keys($data)) .'`' .') VAlUES';
            $sql .= ' (' . $this->buildPlaceholder($data) . ')';
            foreach ($data as $v) {
                $this->buildBindParams($v);
            }
        } else {
            $structure = current($data);
            $sql .= '`' . implode('`,`',array_keys($structure)) .'`' .') VAlUES';
            $placeholder = ' (' . $this->buildPlaceholder($structure) . ')';
            $sql .= $this->buildPlaceholder($data,$placeholder,',');
            foreach ($data as $v) {
                foreach ($v as $val) {
                    $this->buildBindParams($val);
                }
            }
        }
        return $sql;
    }
    private function buildReturning() {
        return empty($this->sqlSegments['returning']) ? '':' RETURNING' . $this->buildIdent($this->sqlSegments['returning']);
    }
    private function buildInsert() {
        return 'INSERT'
        . $this->buildFlags()
        . " INTO "
        . $this->buildTable()
        . $this->buildValuesForInsert()
        . $this->buildReturning();
    }
    private function buildReplace() {
        return 'REPLACE'
        . $this->buildFlags()
        . " INTO "
        . $this->buildTable()
        . $this->buildValuesForInsert()
        . $this->buildReturning();
    }
    private function buildValuesForUpdate() {
        $data = $this->sqlSegments['field'];
        if (empty($data)) {
            return '';
        }
        $sql = ' SET '.$this->buildPairKV($data,' = ',',',null,'?');
        foreach ($data as $v) {
            $this->buildBindParams($v);
        }
        return $sql;
    }
    private function buildUpdate() {
        return 'UPDATE'
        . $this->buildFlags()
        . $this->buildTable()
        . $this->buildValuesForUpdate()
        . $this->buildClause('where')
        . $this->buildOrderBy()
        . $this->buildLimit()
        . $this->buildReturning();
    }
    private function buildDelete() {
        return 'DELETE'
        . $this->buildFlags()
        . ' FROM'
        . $this->buildTable()
        . $this->buildClause('where')
        . $this->buildOrderBy()
        . $this->buildLimit()
        . $this->buildReturning();
    }
    private function addBuildJoin($type = null,$name,$conditionA = null,$logic = null,$conditionB = null) {
        $index = sizeof($this->sqlSegments['join']);
        $this->sqlSegments['join'][$index]['type'] = strtoupper($type);
        $table = explode(' ',trim($name));
        $count = count($table);
        $this->sqlSegments['join'][$index]['table'] = $table[0];
        if ($count == 2) {
            $this->sqlSegments['join'][$index]['alias'] = $table[1];
        } else if ($count == 3) {
            $this->sqlSegments['join'][$index]['alias'] = $table[2];
        }
        $this->sqlSegments['join'][$index]['logic'] = $logic;
        $this->sqlSegments['join'][$index]['conditionA'] = $conditionA;
        $this->sqlSegments['join'][$index]['conditionB'] = $conditionB;
    }
    private function buildJoin() {
        //left join `tab2` as `t1` ON `tab2`.`id` = `tab1`.`uid`
        if (empty($this->sqlSegments['join'])) {
            return '';
        }
        $sql = '';
        foreach ($this->sqlSegments['join'] as $join) {
            $sql .= isset($join['type'])?' ' . $join['type']:'';
            $sql .= ' JOIN';
            if (isset($join['alias'])) {
                $sql .= ' ' .$this->buildIdent($join['table']) . ' AS ' . $this->buildIdent($join['alias']);
            } else {
                $sql .= ' ' .$this->buildIdent($join['table']);
            }
            if (isset($join['conditionA']) && isset($join['conditionB'])) {
                $sql .= ' ON ' . $this->buildIdent($join['conditionA']) . $join['logic'] . $this->buildIdent($join['conditionB']);
            }
        }
        return $sql;
    }
    public function debugDumpParams() {
        return $this->_debugDumpParams;
    }
}
